package controlmercancias.remoto.servidortren;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import controlmercancias.dominio.Coordenada;
import controlmercancias.dominio.Medida;
import controlmercancias.dominio.Sensor;
import controlmercancias.dominio.Tren;
import controlmercancias.dominio.Usuario;
import controlmercancias.util.Message;
import controlmercancias.util.SocketManager;

/**
 *
 * @author Hasier
 */
public class ServidorTren implements Runnable {

    private SocketManager sm;
    private Tren trenActual;
    private Usuario usuarioActual;
    private int estado;
    private SocketManager conexionActualizaciones;
    private File updateFile = new File("receivedUpdates/update.txt");
    private File lastUpdate = new File("receivedUpdates/lastUpdate.txt");

    public ServidorTren(Socket s) throws IOException {
        sm = new SocketManager(s);
    }

    public Usuario getUsuarioActual() {
        return usuarioActual;
    }

    @Override
    public void run() {
        try {
            String linea, comando, usuario = "", clave = "";
            for (estado = 0; estado < 3;) {
                linea = sm.readLine();
                if (linea == null || estado == 4) {
                    break;
                }
                StringTokenizer sTok = new StringTokenizer(linea, " ");
                comando = sTok.nextToken().toUpperCase();

                switch (estado) {
                    case 0:
                        if (comando.equals("USER")) {
                            try {
                                usuario = sTok.nextToken();
                                if ("".equals(usuario.trim())) {
                                    throw new NoSuchElementException();
                                }
                                usuario = usuario.trim();
                                if (ServidorTrenDemon.getAccesoDatos().isUsuario(usuario)) {
                                    sm.write("210 OK Bienvenido " + usuario);
                                    estado = 1;
                                } else {
                                    throw new NoSuchElementException();
                                }
                            } catch (NoSuchElementException ex) {
                                sm.write("411 ERR Falta el nombre de usuario");
                            }
                        } else if (comando.equals("SALIR")) {
                            salir();
                        } else {
                            sm.write("500 ERR Comando incorrecto");
                        }
                        break;

                    case 1:
                        if (comando.equals("PASS")) {
                            try {
                                clave = sTok.nextToken();
                                if ("".equals(clave.trim())) {
                                    throw new NoSuchElementException();
                                }
                                Usuario u = ServidorTrenDemon.getAccesoDatos().getUsuario(usuario, clave);
                                if (u != null) {
                                    usuarioActual = u;
                                    if (ServidorTrenDemon.getConnectedUsers().contains(this)) {
                                        sm.write("436 ERR El usuario " + usuarioActual.getNombre() + " ya está en el sistema");
                                    }
                                    for (Tren t : usuarioActual.getListaTrenes()) {
                                        if (setTren(t)) {
                                            break;
                                        }
                                    }
                                    if (usuarioActual.getListaTrenes().isEmpty()) {
                                        sm.write("435 ERR No tienes trenes asignados");
                                        estado = 99;
                                    } else if (trenActual == null) {
                                        sm.write("434 ERR Todos los trenes están ocupados");
                                        estado = 99;
                                    } else {
                                        sm.write("211 OK Bienvenido al sistema");
                                        estado = 2;
                                        ServidorTrenDemon.trainServerStarted(this, Thread.currentThread());
                                        sendTrenesDeUsuario();
                                    }
                                } else {
                                    sm.write("412 ERR La clave es incorrecta");
                                }
                            } catch (NoSuchElementException ex) {
                                sm.write("413 ERR Falta la clave");
                            }
                        } else if (comando.equals("SALIR")) {
                            salir();
                        } else {
                            sm.write("500 ERR Comando incorrecto");
                        }
                        break;

                    case 2:
                        if (comando.equals("LISTSENSOR")) {
                            sm.write("122 OK Lista de sensores");
                            for (Sensor s : trenActual.getSensores()) {
                                sm.write(s.toString());
                            }
                            sm.write("222 OK Lista finalizada");
                        } else if (comando.equals("HISTORICO")) {
                            String sensorDesconocido = "424 ERR Sensor desconocido";
                            String faltaParametro = "425 ERR Falta el parámetro id_sensor";
                            int id;
                            if ((id = getSensorId(sTok, sensorDesconocido, faltaParametro)) != -1) {
                                boolean ok = checkSensor(id, sensorDesconocido);
                                if (ok) {
                                    Sensor sensor = getSensor(id);
                                    sm.write("123 OK Lista de medidas");
                                    for (Medida m : sensor.getMedidas()) {
                                        sm.write(m.toString());
                                    }
                                    sm.write("222 OK Lista finalizada");
                                }
                            }
                        } else if (comando.equals("ON")) {
                            String sensorDesconocido = "427 ERR Sensor no existe";
                            String faltaParametro = "425 ERR Falta el parámetro id_sensor";
                            int id;
                            if ((id = getSensorId(sTok, sensorDesconocido, faltaParametro)) != -1) {
                                boolean ok = checkSensor(id, sensorDesconocido);
                                if (ok) {
                                    Sensor sensor = getSensor(id);
                                    if (sensor.isEstado()) {
                                        sm.write("428 ERR Sensor en estado ON");
                                    } else {
                                        sensor.setEstado(true);
                                        guardarTrenActual();
                                        sm.write("213 OK Sensor activo");
                                    }
                                }
                            }
                        } else if (comando.equals("OFF")) {
                            String sensorDesconocido = "427 ERR Sensor no existe";
                            String faltaParametro = "425 ERR Falta el parámetro id_sensor";
                            int id;
                            if ((id = getSensorId(sTok, sensorDesconocido, faltaParametro)) != -1) {
                                boolean ok = checkSensor(id, sensorDesconocido);
                                if (ok) {
                                    Sensor sensor = getSensor(id);
                                    if (!sensor.isEstado()) {
                                        sm.write("429 ERR Sensor en estado OFF");
                                    } else {
                                        sensor.setEstado(false);
                                        guardarTrenActual();
                                        sm.write("214 OK Sensor desactivado");
                                    }
                                }
                            }
                        } else if (comando.equals("ONGPS")) {
                            if (trenActual.isGpsConnected()) {
                                sm.write("429 ERR GPS en estado ON");
                            } else {
                                trenActual.setGpsConnected(true);
                                guardarTrenActual();
                                sm.write("215 OK GPS activado");
                            }
                        } else if (comando.equals("OFFGPS")) {
                            if (!trenActual.isGpsConnected()) {
                                sm.write("430 ERR GPS en estado OFF");
                            } else {
                                trenActual.setGpsConnected(false);
                                guardarTrenActual();
                                sm.write("216 OK GPS desactivado");
                            }
                        } else if (comando.equals("GPS")) {
                            sm.write("218 OK Estado GPS " + (trenActual.isGpsConnected() ? "ON" : "OFF"));
                        } else if (comando.equals("GET_VALACT")) {
                            String sensorDesconocido = "424 ERR Sensor desconocido";
                            String faltaParametro = "425 ERR Falta el parámetro id_sensor";
                            int id;
                            if ((id = getSensorId(sTok, sensorDesconocido, faltaParametro)) != -1) {
                                boolean ok = checkSensor(id, sensorDesconocido);
                                if (ok) {
                                    Sensor sensor = getSensor(id);
                                    if (!sensor.isEstado()) {
                                        sm.write("426 ERR Sensor en OFF");
                                    } else {
                                        Medida m = Medida.generateMedida();
                                        sensor.addMedida(m);
                                        guardarTrenActual();
                                        sm.write("124 OK " + m.toString());
                                    }
                                }
                            }
                        } else if (comando.equals("GET_LOC")) {
                            if (trenActual.isGpsConnected()) {
                                Coordenada c = Coordenada.generateCoordenada();
                                guardarTrenActual();
                                sm.write("124 OK " + c.toString());
                            } else {
                                sm.write("435 ERR El GPS no está conectado");
                            }
                        } else if (comando.equals("ACTUALIZAR")) {
                            try {
                                if (!trenActual.getEstado().equals(Tren.EstadoTren.MANTENIMIENTO)) {
                                    sm.write("430 ERR Tren no estado mantenimiento");
                                } else {
                                    getActualizacion();
                                    sm.write("216 OK Actualización completada");
                                }
                            } catch (IOException ex) {
                                sm.write("438 ERR " + Message.IOException.toString());
                            } catch (Exception ex) {
                                sm.write("439 ERR " + ex.getMessage());
                            }

                        } else if (comando.equals("SET_TREN")) {
                            String sensorDesconocido = "432 ERR Tren desconocido";
                            String faltaParametro = "434 ERR Falta el parámetro id_tren";
                            String id;
                            if ((id = getTrenId(sTok, faltaParametro)) != null) {
                                boolean ok = false;
                                for (Tren t : usuarioActual.getListaTrenes()) {
                                    if (id.equals(t.getId())) {
                                        ok = true;
                                        ServidorTrenDemon.getAccesoDatos().guardarDatosTren(trenActual);
                                        if (setTren(t)) {
                                            sm.write("217 OK Tren establecido");
                                            break;
                                        } else {
                                            sm.write("433 ERR Tren en uso por otro usuario");
                                        }
                                    }
                                }
                                if (!ok) {
                                    sm.write(sensorDesconocido);
                                }
                            }
                        } else if (comando.equals("ESTADO")) {
                            sm.write("218 OK " + trenActual.getEstado().toString());
                        } else if (comando.equals("TRENES")) {
                            sendTrenesDeUsuario();
                        } else if (comando.equals("SALIR")) {
                            salir();
                        } else {
                            sm.write("500 ERR Comando incorrecto");
                        }
                        break;
                }
            }
            if (estado == 3) {
                sm.write("218 OK Adios");
            }
            sm.close();
            ServidorTrenDemon.trainServerFinished(this);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (NoSuchElementException nsee) {
            nsee.printStackTrace();
        }
    }

    private void salir() {
        estado = 3;
        guardarTrenActual();
        setTren(null);
    }

    private void guardarTrenActual() {
        ServidorTrenDemon.getAccesoDatos().guardarDatosTren(trenActual);
    }

    private void sendTrenesDeUsuario() throws IOException {
        sm.write("122 OK Lista de trenes");
        for (Tren tren : usuarioActual.getListaTrenes()) {
            sm.write(tren.getId());
        }
        sm.write("222 OK Lista finalizada");
    }

    void forceExit() throws IOException {
        estado = 99;
        sm.write(Message.ForceExit.toString());
    }

    /**
     * Obtiene un sensor a partir de un id
     * @param id El id del sensor
     * @return El sensor, si es que existe. Si no, null
     */
    private Sensor getSensor(int idNum) {
        Sensor sensor = null;
        for (Sensor s : trenActual.getSensores()) {
            if (s.getId() == idNum) {
                sensor = s;
                break;
            }
        }
        return sensor;
    }

    /**
     * Comprueba un sensor
     * @param id El id del sensor
     * @param sensorDesconocido Mensaje a enviar si no se conoce el sensor
     * @return true si el sensor existe. Si ha ocurrido algún error, false
     * @throws IOException 
     */
    private boolean checkSensor(int idNum, String sensorDesconocido) throws IOException {
        try {
            Sensor sensor = getSensor(idNum);
            if (sensor == null) {
                throw new NumberFormatException();
            } else {
                return true;
            }
        } catch (NumberFormatException ex) {
            //sm.write("427 ERR Sensor no existe");
            sm.write(sensorDesconocido);
        }
        return false;
    }

    /**
     * Devuelve el id del sensor que contiene y al que apunta el StringTokenizer
     * @param sTok El StringTokenizer que contiene y apunta al id del sensor
     * @return El id del sensor o -1 si no es correcto
     * @throws IOException 
     */
    private int getSensorId(StringTokenizer sTok, String sensorDesconocido, String faltaParametro) throws IOException {
        try {
            String id = sTok.nextToken();
            if ("".equals(id.trim())) {
                throw new NoSuchElementException();
            }
            int idNum = Integer.parseInt(id);
            return idNum;
        } catch (NoSuchElementException ex) {
            sm.write(faltaParametro);
            return -1;
        } catch (NumberFormatException ex) {
            //sm.write("427 ERR Sensor no existe");
            sm.write(sensorDesconocido);
            return -1;
        }
    }

    private String getTrenId(StringTokenizer sTok, String faltaParametro) throws IOException {
        try {
            String id = sTok.nextToken();
            if ("".equals(id.trim())) {
                throw new NoSuchElementException();
            }
            return id;
        } catch (NoSuchElementException ex) {
            sm.write(faltaParametro);
            return null;
        }
    }

    private void initActualizacion() throws IOException {
        if (lastUpdate.exists()) {
            lastUpdate.delete();
        }
        updateFile.renameTo(lastUpdate);
        updateFile = new File("receivedUpdates/update.txt");
        if (updateFile.exists()) {
            updateFile.delete();
        }
        conexionActualizaciones = new SocketManager(ServidorTrenDemon.getUpdateConnectionIP(), ServidorTrenDemon.getUpdateConnectionPort());
    }

    private void getActualizacion() throws Exception {
        initActualizacion();
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(updateFile);

            conexionActualizaciones.write("USER " + sm.readLine());
            String resp = conexionActualizaciones.readLine();
            if (!resp.contains(Message.OK.toString())) {
                throw new Exception("Usuario no válido");
            }

            conexionActualizaciones.write("PASS " + sm.readLine());
            resp = conexionActualizaciones.readLine();
            if (!resp.contains(Message.OK.toString())) {
                throw new Exception("Contraseña no válida");
            }

            conexionActualizaciones.write("GET_FICHERO");
            resp = conexionActualizaciones.readLine();
            if (!resp.contains(Message.OK.toString())) {
                throw new IOException();
            }


            String bytes;
            while (!(bytes = conexionActualizaciones.readLine()).equals("-1")) {
                fos.write(Integer.parseInt(bytes));
            }
            conexionActualizaciones.write("SALIR");
            conexionActualizaciones.readLine();
            conexionActualizaciones.close();
            if (lastUpdate.exists()) {
                lastUpdate.delete();
            }
        } catch (Exception ex) {
            if (updateFile.exists()) {
                updateFile.delete();
            }
            if (lastUpdate.exists()) {
                lastUpdate.renameTo(updateFile);
            }
            throw ex;
        } finally {
            if (fos != null) {
                fos.flush();
                fos.close();
            }
        }
    }

    public boolean setTren(Tren nuevoTren) {
        if (nuevoTren == null || ServidorTrenDemon.addTrenUsado(nuevoTren.getId())) {
            if (trenActual != null) {
                ServidorTrenDemon.removeTrenUsado(trenActual.getId());
            }
            trenActual = nuevoTren;
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        return usuarioActual.getNombre();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ServidorTren other = (ServidorTren) obj;
        if (!other.getUsuarioActual().equals(this.getUsuarioActual())) {
            return false;
        }
        return true;
    }
}
